// C++ includes
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <csignal>
#include <cassert>
#include <vector>

// UNIX includes
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>

int main(int argc, char *argv[])
{
	// This pipe is written by the TE and read by
	// the executed shell as STDIN.
	int pipeTEtoSH[2];
	
	// This pipe is written by the executed shell
	// as STDOUT and read by the TE.
	int pipeSHtoTE[2];
	
	// This pipe is written by the executed shell
	// as STDERR and read by the TE.
	int pipeSHtoTEerr[2];
	
	// Instantiation of the 3 pipes.
	int r1 = pipe(pipeTEtoSH);
	int r2 = pipe(pipeSHtoTE);
	int r3 = pipe(pipeSHtoTEerr);
	
	// Check that all the pipes were successfully
	// instantiated.
	if (r1 == -1 || r2 == -1 || r3 == -1)
	{
		std::cout << "Can not pipe." << std::endl;
		return -1;
	}
	
	// The shell will be run as a separate process.
	int pid = fork();
	
	// Check that a child process was created.
	if (pid == -1)
	{
		std::cout << "Can not fork." << std::endl;
		return -1;
	}
	
	// From now on the same source code will be
	// shared by two distinct processes; in the
	// parent process fork() returns the PID of
	// the child, while in the child process
	// it returns 0.
	
	if (pid != 0)
	{
		// The code in this if will be executed by the parent.
		// This is the TE logic.
		
		// TE receives data from the shell one char at a time.
		char c;
		
		// TE sends entire commands, possibly containing
		// whitespaces, to the shell.
		std::string cmd;
		
		do
		{
			// Terminal emulator prompt.
			std::cout << "§ " << std::flush;
			std::getline(std::cin, cmd);
			
			if (cmd == "exit")
			{
				// We don't want to execute exit on the shell,
				// because this would terminate the child
				// process while the parent process, the TE,
				// would keep running. Instead, we display an
				// error message.
				
				std::cout << "Emulated terminal - use 'stop' instead." << std::endl;
			}
			else if (cmd == "info")
			{
				// This is a command implemented by the TE; it
				// simply prints an informative message about
				// the terminal emulator.
				
				std::cout << "Damix's Terminal Emulator, 2011." << std::endl;
			}
			else if (cmd != "stop")
			{
				// Any other command different from stop is
				// sent to the shell using the appropriate
				// pipe. The command is terminated by a newline.
				write(pipeTEtoSH[1], cmd.c_str(), cmd.size());
				write(pipeTEtoSH[1], "\n", 1);
				
				// Data structures needed for select() and
				// non-blocking I/O.
				fd_set rfd;
				fd_set wfd;
				fd_set efd;
				timeval tv;

				do {
					// Wait for data available on either the
					// STDOUT pipe or the STDERR pipe.
					FD_ZERO(&rfd);
					FD_ZERO(&wfd);
					FD_ZERO(&efd);
					FD_SET(pipeSHtoTE[0], &rfd);
					FD_SET(pipeSHtoTEerr[0], &rfd);
					tv.tv_sec = 0;
					tv.tv_usec = 100000;
					select(100, &rfd, &wfd, &efd, &tv);
					
					// Check for data on the STDOUT pipe; the
					// executed console will write most of the
					// output on this pipe.
					if (FD_ISSET(pipeSHtoTE[0], &rfd))
					{
						read(pipeSHtoTE[0], &c, 1);
						std::cout << c << std::flush;
					}
					
					// Check for data on the STDERR pipe; the
					// executed console will write error messages
					// on this pipe (for instance the error message
					// that results from typing an unexisting command.
					// Just for display purposes, the STDERR messages
					// will be shown in upper case.
					if (FD_ISSET(pipeSHtoTEerr[0], &rfd))
					{
						read(pipeSHtoTEerr[0], &c, 1);
						std::cout << (char)toupper(c) << std::flush;
					}
					
					// If no data is detected for more than 1/10 of a
					// second, reading is aborted and the TE prompts
					// the user again. The timeout is specified by
					// the tv.tv_sec and tv.tv_usec.
				} while (FD_ISSET(pipeSHtoTE[0], &rfd) || FD_ISSET(pipeSHtoTEerr[0], &rfd));
			}
			
			// Command stop is interpreted by the TE and
			// results in the TE stopping prompting the
			// user and proceed to kill the shell and
			// itself.
		} while (cmd != "stop");

		// Parent TE process kills the child shell.
		kill(pid, SIGKILL);
	}
	else
	{
		// The code in this else will be executed by the child.
		// This is the shell logic.
		
		// Redirect STDIN.
		assert(dup2(pipeTEtoSH[0], 0) == 0);
		
		// Redirect STDOUT.
		assert(dup2(pipeSHtoTE[1], 1) == 1);
		
		// Redirect STDERR.
		assert(dup2(pipeSHtoTEerr[1], 2) == 2);
		
		// The executed shell is bash.
		execlp("bash", "/bin/bash", NULL);
	}
	
	// Everything went fine.
	return 0;
}
